package com.ybt.communicate.tcp.accessor.impl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import com.ybt.communicate.core.exception.ProtocolException;
import com.ybt.communicate.tcp.accessor.IDataAccessStrategy;

/**
 * 定长读取策略，策略标示(length:begin,end)
 * 
 * @author wuxiao
 * 
 */
public class LengthFixedAccessor implements IDataAccessStrategy {
    private static Logger logger = Logger
    .getLogger(LengthFixedAccessor.class);
    private String strategy;
    private String charset;
    private int begin;
    private int end;
    private int head;
    private int length;

    private LengthFixedAccessor(int head, int begin, int end) {
        this.begin = begin;
        this.end = end;
        this.head = head;
        this.length = end - begin + 1;
    }

    public byte[] receive(ReadableByteChannel channel) throws ProtocolException {
        // TODO Auto-generated method stub
        // 消息体body长度
        int body = 0;
        byte[] headContent = null;
        try {
            // 头部信息
            ByteBuffer headBuffer = ByteBuffer.allocate(head);
            //循环读取消息，直到读完消息头
            while (headBuffer.hasRemaining() && channel.read(headBuffer) != -1)
                ;

            if (!headBuffer.hasRemaining()) {
                // 准备读取缓冲区
                headBuffer.flip();
                // 读取body长度
                headContent = headBuffer.array();
                byte[] bodyLength = new byte[length];
                System.arraycopy(headContent, begin, bodyLength, 0, length);
                body = Integer.valueOf(new String(bodyLength,charset).trim());

                if (body == 0) {
                    //如果没有消息体
                    logger.warn("the message don't contain body part");
                    return headContent;
                }
            } else {
                // 消息头长度不够
                throw new ProtocolException(
                        "the message head don't fits the format[" + strategy
                                + "] ");
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            throw new ProtocolException(e);
        }

        /**
         * 读取消息体
         */
        ByteBuffer bodyBuffer = ByteBuffer.allocate(body);
        try {
            //读写消息体
            while (bodyBuffer.hasRemaining() && channel.read(bodyBuffer) != -1)
                if (bodyBuffer.hasRemaining()) {
                    //消息体长度不够
                    throw new ProtocolException(
                            "the boy don't contain enough content of length : ["
                                    + body + "] ");
                }
            //准备提取消息体
            bodyBuffer.flip();
            ByteArrayOutputStream result = new ByteArrayOutputStream();
            WritableByteChannel out = Channels.newChannel(result);
            //写入头部
            out.write(ByteBuffer.wrap(headContent));
            
            //写入报文体
            out.write(bodyBuffer);
            
            byte[] contents = result.toByteArray();
            if(logger.isDebugEnabled()){
                logger.debug("received total "+ contents.length +" bytes");
            }
            return contents;
        } catch (IOException e) {
            // TODO Auto-generated catch block
            throw new ProtocolException(e);
        }
    }

    public void send(WritableByteChannel channel, byte[] data)
            throws ProtocolException {
        // TODO Auto-generated method stub
        try {
            //写入消息体
            ByteBuffer bodyBuffer = ByteBuffer.wrap(data);
            while (bodyBuffer.hasRemaining()){
                //buffer还没有写完(非阻塞io不一定一次写完)
                channel.write(bodyBuffer);
            }
            if(logger.isDebugEnabled()){
                logger.debug("send total "+ data.length +" bytes successfully");
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            throw new ProtocolException(e);
        }
    }

    /**
     * 根据定长策略，初始化定长策略数据读写器
     * 
     * @param strategy
     *            定长读写策略（head:\\d+;length:begin,end）
     * @return 定长数据读写器
     */
    public static LengthFixedAccessor newInstance(String strategy, String charset) {
        Pattern p = Pattern.compile("^head:(\\d+);length:(\\d+),(\\d+)$");
        Matcher m = p.matcher(strategy);
        if (m.find()) {
            int head = Integer.valueOf(m.group(1));
            int begin = Integer.valueOf(m.group(2));
            int end = Integer.valueOf(m.group(3));
            LengthFixedAccessor accessor = new LengthFixedAccessor(head, begin, end);
            accessor.setCharset(charset);
            return accessor;
        }
        throw new RuntimeException("unsupport access strategy ["+strategy+"]");
    }

    public String getStrategy() {
        return strategy;
    }

    public void setStrategy(String strategy) {
        this.strategy = strategy;
    }

    public String getCharset() {
        return charset;
    }

    public void setCharset(String charset) {
        this.charset = charset;
    }

    public String toString() {
        return "length:" + begin + "," + end;
    }

    public static void main(String[] args) {
       // ByteArrayOutputStream in = new ByteArrayOutputStream();
       // WritableByteChannel out = Channels.newChannel(in);
//        LengthUnknownAccessor acc = LengthUnknownAccessor.newInstance(null);
        LengthFixedAccessor acc = LengthFixedAccessor.newInstance("head:3;length:0,3",Charset.defaultCharset().name());
        /*try {
            acc.send(out,"009123456789".getBytes());
            System.out.println(new String(in.toByteArray()));
        } catch (ProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }*/
       /* ByteArrayInputStream in = new ByteArrayInputStream("009123456789".getBytes(Charset.defaultCharset()));
        ReadableByteChannel out = Channels.newChannel(in);
        try {
            System.out.println(new String(acc.receive(out),Charset.defaultCharset()));
        } catch (ProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }*/

    }

}
